package com.zdp.rest;

import com.zdp.commons.GraceJSONResult;
import com.zdp.commons.ResponseStatusEnum;
import com.zdp.commons.exception.GraceException;
import com.zdp.commons.util.JwtUtil;
import com.zdp.domain.Auth;
import com.zdp.domain.MfaType;
import com.zdp.domain.User;
import com.zdp.domain.dto.LoginDto;
import com.zdp.domain.dto.SendTotpDto;
import com.zdp.domain.dto.TotpVerificationDto;
import com.zdp.domain.dto.UserDto;
import com.zdp.service.SmsService;
import com.zdp.service.UserCacheService;
import com.zdp.service.UserService;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.util.Pair;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.nio.file.AccessDeniedException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * @author sesshomaru
 * @date 2021/7/5
 */
@Slf4j
@RestController
@RequestMapping("/authorize")
public class AuthorizeResource {

    @Autowired
    private UserService userService;
    @Autowired
    private JwtUtil jwtUtil;
    @Autowired
    private UserCacheService userCacheService;

    @PostMapping("/register")
    public GraceJSONResult register(@Valid @RequestBody UserDto userDto, BindingResult result) {
        // 判断BindingResult是否保存错误的验证信息，如果有，则直接return
        if (result.hasErrors()) {
            Map<String, String> errorMap = getErrors(result);
            return GraceJSONResult.errorMap(errorMap);
        }
        // username、email、mobile 不能重复
        if (userService.isUserNameExisted(userDto.getUsername())
                || userService.isEmailExisted(userDto.getEmail())
                || userService.isMobileExisted(userDto.getMobile())) {
            GraceException.display(ResponseStatusEnum.USER_STATUS_ERROR);
        }

        User user = new User();
        BeanUtils.copyProperties(userDto, user);

        return GraceJSONResult.ok(userService.register(user));
    }

    // 获取token
    @PostMapping("/token")
    public ResponseEntity<?> login(@RequestBody LoginDto loginDto) {
        // Auth auth = userService.login(loginDto.getUsername(), loginDto.getPassword());
        User user = userService.findOptionalByUsernameAndPassword(loginDto.getUsername(), loginDto.getPassword());
        if (Optional.ofNullable(user).isPresent()) {
            // 1.升级密码
            userService.upgradePasswordEncodingIfNeeded(user, loginDto.getPassword());
            // 2.验证
            if (!user.isEnabled()) {
                throw new RuntimeException("用户状态异常");
            }
            if (!user.isAccountNonLocked()) {
                throw new RuntimeException("用户状态异常");
            }
            if (!user.isAccountNonExpired()) {
                throw new RuntimeException("用户状态异常");
            }
            // 3.判断 usingMfa如果是false就直接返回token
            if (!user.isUsingMfa()) {
                return ResponseEntity.ok().body(userService.login(loginDto.getUsername(), loginDto.getPassword()));
            }
            // 4.使用多因子登录
            val mfaId = userCacheService.cacheUser(user);
            // 5.响应携带了X-Authenticate表示启用多因子登录，前端需要跳转到对应的选择登录方式页面
            return ResponseEntity
                    .status(HttpStatus.UNAUTHORIZED)
                    .header("X-Authenticate", "mfa", "realm=" + mfaId)
                    .build();
        }
        return null;
    }

    @PutMapping("/totp")
    public GraceJSONResult sendTotp(@Valid @RequestBody SendTotpDto sendTotpDto, BindingResult result){
        // 判断BindingResult是否保存错误的验证信息，如果有，则直接return
        if (result.hasErrors()) {
            Map<String, String> errorMap = getErrors(result);
            return GraceJSONResult.errorMap(errorMap);
        }
        userCacheService.retrieveUser(sendTotpDto.getMfaId())
                .flatMap(user -> userService.createTotp(user.getMfaKey()).map(totp -> Pair.of(user, totp)))
                .ifPresentOrElse(pair -> {
                    if (sendTotpDto.getMfaType() == MfaType.SMS) {
                        // 发送短信
                        log.info("发送短信 手机号:{}, MfaKey:{}", pair.getFirst().getMobile(), pair.getSecond());
                    } else if (sendTotpDto.getMfaType() == MfaType.EMAIL) {
                        // 发送邮件
                        log.info("发送邮件 邮箱:{}, MfaKey:{}", pair.getFirst().getEmail(), pair.getSecond());
                    }
                }, () -> {
                    throw new RuntimeException("用户状态异常");
                });

        return GraceJSONResult.ok();
    }

  /*  @PostMapping("/totp")
    public Auth verifyTotp(@Valid @RequestBody TotpVerificationDto totpVerificationDto) {

    }*/

    @PostMapping("/checkToken")
    public GraceJSONResult checkToken(@RequestParam String token){
        return GraceJSONResult.ok(jwtUtil.validateAccessToken(token));
    }

    // 刷新token
    @PostMapping("/token/refresh")
    public GraceJSONResult refreshToken(@RequestHeader(name = "Authorization")String authorization,
                                        @RequestParam String refreshToken) throws AccessDeniedException {
        val PREFIX = "Bearer ";
        val accessToken = authorization.replace(PREFIX, "");
        // 校验token
        if (jwtUtil.validateAccessToken(accessToken) && jwtUtil.validateRefreshToken(refreshToken)) {
            // 签发新的token
            Auth auth = new Auth(jwtUtil.createAccessTokenWithRefreshToken(refreshToken), refreshToken);
            return GraceJSONResult.ok(auth);
        }
        throw new AccessDeniedException("访问被拒绝");
    }


    /**
     * 获取BO中的错误信息
     * @param result
     */
    public Map<String, String> getErrors(BindingResult result) {
        Map<String, String> map = new HashMap<>();
        List<FieldError> errorList = result.getFieldErrors();
        for (FieldError error : errorList) {
            // 发送验证错误的时候所对应的某个属性
            String field = error.getField();
            // 验证的错误消息
            String msg = error.getDefaultMessage();
            map.put(field, msg);
        }
        return map;
    }
}
